﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Graphics.PackedVector;

namespace ProceduralGolf
{
    class GolfCourseLayout
    {
        private Game game;

        public enum GolfCourseLayer {ROUGH, FRINGE, FAIRWAY, GREEN, HAZARDS};
        public const int LayerCount = 5;

        public List<Vector2> Points { get { return points; } }
        private List<Vector2> points;

        public List<Circle> Fairway { get { return fairway; } }
        private List<Circle> fairway;

        public List<Circle> Hazards { get { return hazards; } }
        private List<Circle> hazards;

        public List<Circle> Green { get { return green; } }
        private List<Circle> green;

        public Vector2 CourseTee { get { return tee; } }
        private Vector2 tee;
        public Vector2 CourseHole { get { return hole; } }
        private Vector2 hole;

        public Vector2 Tee { get { return offset + tee; } }
        public Vector2 Hole { get { return offset + hole; } }

        public Vector2 CourseSize { get { return course_size;} }
        private Vector2 course_size;

        public Vector2 TerrainSize { get { return terrain_size; } }
        private Vector2 terrain_size;

        public int Height { get { return (int)course_size.Y; } }
        public int Width { get { return (int)course_size.X; } }

        public Vector2 OffSet { get { return offset; } }
        private Vector2 offset;

        private int par;
        public int Par { get { return par; } }

        TextureCircleDrawer drawer;
        TextureBlurrer blurrer;
        TextureThresholder thresholder;
        TextureInverter inverter;
        TextureColorer colorer;
        TextureDifferencer differ;

        public GolfCourseLayout(Game game, Vector2 tee, Vector2 hole, Vector2 course_size, Vector2 terrain_size, int par)
        {
            this.game = game;
            this.course_size = course_size;
            this.terrain_size = terrain_size;
            this.par = par;
            offset = calc_offset();

            this.tee = tee;
            this.hole = hole;

            points = new List<Vector2>();
            fairway = new List<Circle>();
            hazards = new List<Circle>();
            green = new List<Circle>();

            drawer = new TextureCircleDrawer(game);
            blurrer = new TextureBlurrer(game);
            thresholder = new TextureThresholder(game);
            inverter = new TextureInverter(game);
            colorer = new TextureColorer(game);
            differ = new TextureDifferencer(game);
        }

        private Vector2 calc_offset()
        {
            float offset_x = (terrain_size.X/2.0f) - (course_size.X/2.0f);
            float offset_y = (terrain_size.Y/2.0f) - (course_size.Y/2.0f);
            return new Vector2(offset_x, offset_y);
        }

        public void addPoint(Vector2 point)
        {
            points.Add(point);
        }

        public void addFairway(Circle point)
        {
            fairway.Add(point);
        }

        public void addHazard(Circle point)
        {
            hazards.Add(point);
        }

        public void addGreen(Circle point)
        {
            green.Add(point);
        }

        private Texture2D drawLayer(List<Circle> circles)
        {
            Texture2D tex = new Texture2D(game.GraphicsDevice, (int)course_size.X, (int)course_size.Y, 1, TextureUsage.None, SurfaceFormat.Color);
            foreach (Circle c in circles)
            {
                tex = drawer.draw(tex, c, Color.White);
            }
            tex = blurrer.blur(tex, 3);
            tex = thresholder.threshold(tex, 0.45f);
            return tex;
        }

        //public Texture2D getGreenLayer()
        //{
        //    return drawLayer(green);
        //}

        //public Texture2D getFairwayLayer()
        //{
        //    return drawLayer(fairway);
        //}

        //public Texture2D getFringeHazardLayer()
        //{
        //    TextureBlurrer blurrer = new TextureBlurrer(game);
        //    TextureThresholder thresholder = new TextureThresholder(game);
        //    TextureDifferencer diff = new TextureDifferencer(game);
        //    TextureInverter inverter = new TextureInverter(game);
        //    TextureBlender blender = new TextureBlender(game);

        //    Texture2D hazards = getHazardLayer();
        //    Texture2D fringe = blurrer.blur(hazards, 2);
        //    fringe = thresholder.threshold(fringe, 0.03f);
        //    fringe = diff.diff(fringe, hazards);

        //    return fringe;
        //}

        //public Texture2D getFringeOutLayer()
        //{
        //    TextureBlurrer blurrer = new TextureBlurrer(game);
        //    TextureThresholder thresholder = new TextureThresholder(game);
        //    TextureDifferencer diff = new TextureDifferencer(game);
        //    TextureInverter inverter = new TextureInverter(game);
        //    TextureBlender blender = new TextureBlender(game);

        //    Texture2D fairway = this.getFairwayLayer();
        //    Texture2D fringe = blurrer.blur(fairway, 2);
        //    fringe = thresholder.threshold(fringe, 0.05f);
        //    fringe = diff.diff(fringe, fairway);
        //    return fringe;
        //}

        public Texture2D getHazardLayer()
        {
            return drawLayer(hazards);
        }

        public Texture2D getLayout()
        {
            Texture2D layout = new Texture2D(game.GraphicsDevice, (int)course_size.X, (int)course_size.Y, 1, TextureUsage.None, SurfaceFormat.Color);
            Texture2D fairway = this.getFairwayLayer();
            Texture2D green = this.getGreenLayer();
            Texture2D hazards = this.getHazardLayer();
            Texture2D fringe = this.getFringeLayer();

            Texture2D fringeMask = inverter.invert(fringe);
            layout = colorer.color(layout, fringeMask, new Color(0.0f, 0.5f, 0.0f, 1.0f));

            Texture2D fairwayMask = inverter.invert(fairway);
            layout = colorer.color(layout, fairwayMask, new Color(0.0f, 0.8f, 0.0f, 1.0f));

            Texture2D greenMask = inverter.invert(green);
            layout = colorer.color(layout, greenMask, new Color(0.0f, 3.0f, 0.0f, 1.0f));

            Texture2D hazardsMask = inverter.invert(hazards);
            layout = colorer.color(layout, hazardsMask, Color.Gold);

            //draw tee
            layout = drawer.draw(layout, new Circle(tee, 5), Color.Black);
            //draw hole
            layout = drawer.draw(layout, new Circle(hole, 5), Color.Red);

            return layout;
        }

        public Texture2D getLayout(IDictionary<GolfCourseLayer, Color> colors)
        {
            Texture2D layout = new Texture2D(game.GraphicsDevice, (int)course_size.X, (int)course_size.Y, 1, TextureUsage.None, SurfaceFormat.Color);

            Texture2D layerTexture;
            Texture2D layerMask;
            foreach (GolfCourseLayer layer in EnumHelper.GetEnumValues(typeof(GolfCourseLayer)))
            {
                layerTexture = getLayer(layer);
                layerMask = inverter.invert(layerTexture);
                Color color = colors.ContainsKey(layer) ? colors[layer] : Color.TransparentBlack;
                layout = colorer.color(layout, layerMask, color);
            }

            Color[] layoutData = new Color[layout.Width * layout.Height];
            layout.GetData(layoutData);
            Rectangle rect = new Rectangle((int)offset.X, (int)offset.Y, (int)course_size.X, (int)course_size.Y);

            Texture2D fullLayout = new Texture2D(game.GraphicsDevice, (int)terrain_size.X, (int)terrain_size.Y, 1, TextureUsage.None, SurfaceFormat.Color);
            fullLayout = colorer.color(fullLayout, colors[GolfCourseLayer.ROUGH]);
            fullLayout.SetData(0, rect, layoutData, 0, layoutData.Length, SetDataOptions.None);
            return fullLayout;
        }

        public Texture2D getFairwayLayer()
        {
            Texture2D tex = new Texture2D(game.GraphicsDevice, (int)course_size.X, (int)course_size.Y, 1, TextureUsage.None, SurfaceFormat.Color);

            //foreach (Circle c in fairway)
            //    tex = drawer.draw(tex, c, Color.White);
            //foreach (Circle c in hazards)
            //    tex = drawer.draw(tex, c, Color.Black);
            tex = drawer.draw(tex, fairway, Color.White);
            tex = drawer.draw(tex, hazards, Color.Black);

            tex = blurrer.blur(tex, 3);
            tex = thresholder.threshold(tex, 0.85f);

            return tex;
        }


        public Texture2D getFringeLayer()
        {
            Texture2D tex = new Texture2D(game.GraphicsDevice, (int)course_size.X, (int)course_size.Y, 1, TextureUsage.None, SurfaceFormat.Color);

            foreach (Circle c in fairway)
                tex = drawer.draw(tex, c, Color.White);
            foreach (Circle c in hazards)
                tex = drawer.draw(tex, c, Color.White);

            tex = blurrer.blur(tex, 3);
            tex = thresholder.threshold(tex, 0.50f);
            return tex;
        }

        public Texture2D getGreenLayer()
        {
            
            Texture2D tex = new Texture2D(game.GraphicsDevice, (int)course_size.X, (int)course_size.Y, 1, TextureUsage.None, SurfaceFormat.Color);

            tex = differ.diff(getFringeLayer(), getFairwayLayer());
            Texture2D greenTex = new Texture2D(game.GraphicsDevice, (int)course_size.X, (int)course_size.Y, 1, TextureUsage.None, SurfaceFormat.Color);
            foreach (Circle c in green)
                greenTex = drawer.draw(greenTex, c, Color.White);

            tex = differ.diff(greenTex, tex);

            tex = blurrer.blur(tex, 3);
            tex = thresholder.threshold(tex, 0.40f);

            return tex;
        }

        public Texture2D getRoughLayer()
        {
            Texture2D tex = new Texture2D(game.GraphicsDevice, (int)course_size.X, (int)course_size.Y, 1, TextureUsage.None, SurfaceFormat.Color);
            tex = colorer.color(tex, Color.White);
            return tex;
        }

        public Texture2D getLayer(GolfCourseLayer layer)
        {
            switch (layer)
            {
                case GolfCourseLayer.FAIRWAY:
                    return getFairwayLayer();
                case GolfCourseLayer.FRINGE:
                    return getFringeLayer();
                case GolfCourseLayer.GREEN:
                    return getGreenLayer();
                case GolfCourseLayer.HAZARDS:
                    return getHazardLayer();
                case GolfCourseLayer.ROUGH:
                    return getRoughLayer();
                default:
                    return null;
            }

        }


        public GolfCourseLayer[,] getGolfCourseLayout()
        {
            GolfCourseLayer[,] layout = new GolfCourseLayer[(int)terrain_size.X, (int)terrain_size.Y];

            int cw = (int)course_size.X;
            int ch = (int)course_size.Y;
            Color[] data = new Color[cw * ch];
            foreach (GolfCourseLayer layer in EnumHelper.GetEnumValues(typeof(GolfCourseLayer)))
            {
                getLayer(layer).GetData(data);
                for (int x = 0; x < cw; x++)
                {
                    for (int y = 0; y < ch; y++)
                    {
                        if (data[y * cw + x] == Color.White) layout[x+(int)offset.X, y+(int)offset.Y] = layer;
                    }
                }
            }
            return layout;
        }

    }
}
